/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: fat file system
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-08-15     JasonHu           Port from xboot
 */

/*
 * kernel/vfs/fat/fat.c
 *
 * Copyright(c) 2007-2022 Jianjun Jiang <8192542@qq.com>
 * Official site: http://xboot.org
 * Mobile phone: +86-18665388956
 * QQ: 8192542
 *
 * Permission is hereby granted, NX_MemFree of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "fat-control.h"
#include "fat-node.h"
#include "fat.h"
#include <base/malloc.h>
#include <base/memory.h>
#include <base/initcall.h>
#include <base/div.h>
#include <base/byteorder.h>
#include <base/memory.h>
#include <base/log.h>

NX_PRIVATE NX_Error FatfsMount(NX_MountDesc * m, const char * dev)
{
	struct fatfs_control_t * ctrl;
	struct fatfs_node_t * root;
	int rc;

	if(dev == NX_NULL)
		return NX_EINVAL;

	ctrl = NX_MemAlloc(sizeof(struct fatfs_control_t));
	if(!ctrl)
		return NX_ENOMEM;

	/* Setup control info */
	rc = fatfs_control_init(ctrl, m->dev);
	if(rc)
		goto fail;

	/* Get the root fatfs node */
	root = m->root->data;
	rc = fatfs_node_init(ctrl, root);
	if(rc)
		goto fail;

	/* Handcraft the root fatfs node */
	root->parent = NX_NULL;
	root->parent_dent_off = 0;
	root->parent_dent_len = sizeof(struct fat_dirent_t);
	NX_MemSet(&root->parent_dent, 0, sizeof(struct fat_dirent_t));

	if(ctrl->type == FAT_TYPE_32)
	{
		root->first_cluster = ctrl->first_root_cluster;
	}
	else
	{
		root->first_cluster = 0x0;
	}
	root->cur_cluster = root->first_cluster;
	root->cur_pos = 0;
	root->parent_dent_dirty = NX_False;

	/* Handcraft the root vfs node */
	m->root->type = NX_FILE_NODE_TYPE_DIR;
	m->root->mode = NX_FILE_TYPE_DIR | NX_FILE_MODE_MASK;
	m->root->ctime = 0;
	m->root->atime = 0;
	m->root->mtime = 0;
	m->root->size = 0;

	/* Save control as mount point data */
	m->data = ctrl;
    
	return NX_EOK;

fail:
	NX_MemFree(ctrl);
	return NX_ERROR;
}

NX_PRIVATE NX_Error FatfsUnmount(NX_MountDesc * m)
{
	int rc;
	struct fatfs_control_t *ctrl = m->data;

	if(!ctrl)
		return NX_EINVAL;

	rc = fatfs_control_exit(ctrl);
	NX_MemFree(ctrl);
    
    return rc ? NX_ERROR : NX_EOK;
}

NX_PRIVATE NX_Error FatfsMountSync(NX_MountDesc * m)
{
	struct fatfs_control_t * ctrl = m->data;

	if(!ctrl)
		return NX_EINVAL;
	if (fatfs_control_sync(ctrl))
    {
        return NX_ERROR;
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error FatfsGetNode(NX_MountDesc * m, NX_FileNode * n)
{
	int rc;
	struct fatfs_node_t * node;
	struct fatfs_control_t *ctrl = m->data;

	node = NX_MemAlloc(sizeof(struct fatfs_node_t));
	if(!node)
	{
		return NX_ENOMEM;
	}

	rc = fatfs_node_init(ctrl, node);

	n->data = node;
    return rc ? NX_ERROR : NX_EOK;
}

NX_PRIVATE NX_Error FatfsPutNode(NX_MountDesc * m, NX_FileNode * n)
{
	int rc;
	struct fatfs_node_t * node = n->data;

	if(!node)
		return NX_EINVAL;

	rc = fatfs_node_exit(node);
	NX_MemFree(node);
	return rc ? NX_ERROR : NX_EOK;
}

NX_PRIVATE NX_U64 FatfsRead(NX_FileNode * n, NX_I64 off, void * buf, NX_U64 len, NX_Error *outErr)
{
	struct fatfs_node_t * node = n->data;
	NX_U32 filesize = fatfs_node_get_size(node);

	if(filesize <= (NX_U32) off)
    {
        NX_ErrorSet(outErr, NX_EINVAL);
		return 0;
    }

	if(filesize < (NX_U32) (len + off))
		len = filesize - off;

    NX_ErrorSet(outErr, NX_EOK);
	return fatfs_node_read(node, (NX_U32) off, len, buf);
}

NX_PRIVATE NX_U64 FatfsWrite(NX_FileNode * n, NX_I64 off, void * buf, NX_U64 len, NX_Error *outErr)
{
	NX_U32 wlen;
	struct fatfs_node_t *node = n->data;
    NX_U32 year, month, day, hour, min, second;

	wlen = fatfs_node_write(node, (NX_U32) off, len, buf);

	/* Size and mtime might have changed */
	n->size += wlen;
    fatfs_current_timestamp(&year, &month, &day, &hour, &min, &second);
    n->mtime = fatfs_pack_timestamp(year, month, day, hour, min, second);

    NX_ErrorSet(outErr, NX_EOK);
	return wlen;
}

NX_PRIVATE NX_Error FatfsTruncate(NX_FileNode * n, NX_I64 off)
{
	int rc;
	struct fatfs_node_t * node = n->data;
    NX_U32 year, month, day, hour, min, second;

	if((NX_U32) off > fatfs_node_get_size(node))
		return NX_EINVAL;
	else if((NX_U32) off == fatfs_node_get_size(node))
		return NX_EOK;

	rc = fatfs_node_truncate(node, (NX_U32) off);
	if(rc)
		return NX_ERROR;

	/* Size and mtime might have changed */
	n->size = off;

    fatfs_current_timestamp(&year, &month, &day, &hour, &min, &second);
    n->mtime = fatfs_pack_timestamp(year, month, day, hour, min, second);
	
	return NX_EOK;
}

NX_PRIVATE NX_Error FatfsSync(NX_FileNode * n)
{
	struct fatfs_node_t *node = n->data;
	NX_U32 year, mon, day, hour, min, sec;
	NX_U8 fileattr;

	if(!node)
		return NX_EINVAL;

	fileattr = 0;
	switch(n->type)
	{
	case NX_FILE_NODE_TYPE_DIR:
		fileattr |= FAT_DIRENT_SUBDIR;
		break;
	case NX_FILE_NODE_TYPE_REG:
	default:
		break;
	}

	if(!(n->mode & (NX_FILE_MODE_WRITE)))
		fileattr |= FAT_DIRENT_READONLY;

	node->parent_dent.file_attributes = fileattr;

	/* Update node size */
	if(!(node->parent_dent.file_attributes & FAT_DIRENT_SUBDIR))
	{
		node->parent_dent.file_size = NX_CpuToLe32(n->size);
	}

	/* Update the first cluster */
	node->parent_dent.first_cluster_hi = ((node->first_cluster >> 16) & 0xFFFF);
	node->parent_dent.first_cluster_lo = (node->first_cluster & 0xFFFF);

	/* Update node modify time */
	fatfs_timestamp((NX_TimeStamp *)&n->mtime, &year, &mon, &day, &hour, &min, &sec);
	node->parent_dent.lmodify_date_year = year;
	node->parent_dent.lmodify_date_month = mon;
	node->parent_dent.lmodify_date_day = day;
	node->parent_dent.lmodify_time_hours = hour;
	node->parent_dent.lmodify_time_minutes = min;
	node->parent_dent.lmodify_time_seconds = sec;

	/* Update node access time */
	fatfs_timestamp((NX_TimeStamp *)&n->atime, &year, &mon, &day, &hour, &min, &sec);
	node->parent_dent.laccess_date_year = year;
	node->parent_dent.laccess_date_month = mon;
	node->parent_dent.laccess_date_day = day;

	/* Update node access time */
	fatfs_timestamp((NX_TimeStamp *)(&n->ctime), &year, &mon, &day, &hour, &min, &sec);
	node->parent_dent.create_date_year = year;
	node->parent_dent.create_date_month = mon;
	node->parent_dent.create_date_day = day;
	node->parent_dent.create_time_hours = hour;
	node->parent_dent.create_time_minutes = min;
	node->parent_dent.create_time_seconds = sec;
	node->parent_dent.create_time_millisecs = 0;

	return fatfs_node_sync(node) ? NX_ERROR : NX_EOK;
}

NX_PRIVATE NX_Error FatfsReadDir(NX_FileNode * dn, NX_I64 off, NX_Dirent * d)
{
	struct fatfs_node_t * dnode = dn->data;

	return fatfs_node_read_dirent(dnode, off, d) ? NX_ERROR : NX_EOK;
}

NX_PRIVATE NX_Error FatfsLookup(NX_FileNode * dn, const char * name, NX_FileNode * n)
{
	int rc;
	NX_U32 off, len;
	struct fat_dirent_t dent;
	struct fatfs_node_t *node = n->data;
	struct fatfs_node_t *dnode = dn->data;

	rc = fatfs_node_find_dirent(dnode, name, &dent, &off, &len);
	if(rc)
		return NX_ERROR;

	node->ctrl = dnode->ctrl;
	node->parent = dnode;
	node->parent_dent_off = off;
	node->parent_dent_len = len;
	NX_MemCopy(&node->parent_dent, &dent, sizeof(struct fat_dirent_t));

	if(dnode->ctrl->type == FAT_TYPE_32)
	{
		node->first_cluster = NX_Le16ToCpu(dent.first_cluster_hi);
		node->first_cluster = node->first_cluster << 16;
	}
	else
	{
		node->first_cluster = 0;
	}
	node->first_cluster |= NX_Le16ToCpu(dent.first_cluster_lo);
	node->cur_cluster = node->first_cluster;
	node->cur_pos = 0;

	n->mode = 0;

	if(dent.file_attributes & FAT_DIRENT_SUBDIR)
	{
		n->type = NX_FILE_NODE_TYPE_DIR;
		n->mode |= NX_FILE_TYPE_DIR;
	}
	else
	{
		n->type = NX_FILE_NODE_TYPE_REG;
		n->mode |= NX_FILE_TYPE_REG;
	}

	n->mode |= (NX_FILE_MODE_MASK);
	if(dent.file_attributes & FAT_DIRENT_READONLY)
	{
		n->mode &= ~(NX_FILE_MODE_WRITE);
	}

	n->ctime = fatfs_pack_timestamp(dent.create_date_year, dent.create_date_month, dent.create_date_day, dent.create_time_hours, dent.create_time_minutes,
	        dent.create_time_seconds);
	n->atime = fatfs_pack_timestamp(dent.laccess_date_year, dent.laccess_date_month, dent.laccess_date_day, 0, 0, 0);
	n->mtime = fatfs_pack_timestamp(dent.lmodify_date_year, dent.lmodify_date_month, dent.lmodify_date_day, dent.lmodify_time_hours,
	        dent.lmodify_time_minutes, dent.lmodify_time_seconds);

	n->size = fatfs_node_get_size(node);

	return NX_EOK;
}

NX_PRIVATE NX_Error FatfsCreate(NX_FileNode * dn, const char * name, NX_U32 mode)
{
	int rc;
	NX_U8 fileattr;
	NX_U32 off, len;
	NX_U32 year, mon, day, hour, min, sec;
	struct fat_dirent_t dent;
	struct fatfs_node_t *dnode = dn->data;

	rc = fatfs_node_find_dirent(dnode, name, &dent, &off, &len);
	if(rc != -1)
	{
        if(!rc)
			return NX_EPERM;
		else
			return NX_ERROR;
	}

	fatfs_current_timestamp(&year, &mon, &day, &hour, &min, &sec);

	fileattr = 0;
	if(!(mode & (NX_FILE_MODE_WRITE)))
	{
		fileattr |= FAT_DIRENT_READONLY;
	}

	NX_MemSet(&dent, 0, sizeof(dent));
	dent.file_attributes = fileattr;
	dent.create_time_millisecs = 0;
	dent.create_time_seconds = sec;
	dent.create_time_minutes = min;
	dent.create_time_hours = hour;
	dent.create_date_day = day;
	dent.create_date_month = mon;
	dent.create_date_year = year;
	dent.laccess_date_day = day;
	dent.laccess_date_month = mon;
	dent.laccess_date_year = year;
	dent.first_cluster_hi = 0;
	dent.lmodify_time_seconds = sec;
	dent.lmodify_time_minutes = min;
	dent.lmodify_time_hours = hour;
	dent.lmodify_date_day = day;
	dent.lmodify_date_month = mon;
	dent.lmodify_date_year = year;
	dent.first_cluster_lo = 0;
	dent.file_size = 0;

	rc = fatfs_node_add_dirent(dnode, name, &dent);
	if(rc)
		return NX_ERROR;

	return NX_EOK;
}

NX_PRIVATE NX_Error FatfsDelete(NX_FileNode * dn, NX_FileNode * n, const char *name)
{
	int rc;
	NX_U32 off, len, clust;
	struct fat_dirent_t dent;
	struct fatfs_node_t *dnode = dn->data;
	struct fatfs_node_t *node = n->data;

	rc = fatfs_node_find_dirent(dnode, name, &dent, &off, &len);
	if(rc)
		return NX_ERROR;

	if(dnode->ctrl->type == FAT_TYPE_32)
	{
		clust = NX_Le16ToCpu(dent.first_cluster_hi);
		clust = clust << 16;
	}
	else
	{
		clust = 0;
	}
	clust |= NX_Le16ToCpu(dent.first_cluster_lo);

	if(node->first_cluster != clust)
		return NX_EFAULT;

	rc = fatfs_node_del_dirent(dnode, name, off, len);
	if(rc)
		return rc ? NX_ERROR : NX_EOK;

	return NX_EOK;
}

NX_PRIVATE NX_Error FatfsRename(NX_FileNode * sv, const char *sname, NX_FileNode * n, NX_FileNode * dn, const char *dname)
{
	int rc;
	NX_U32 off, len;
	struct fat_dirent_t dent;
	struct fatfs_node_t *snode = sv->data;
	struct fatfs_node_t *dnode = dn->data;

	rc = fatfs_node_find_dirent(dnode, dname, &dent, &off, &len);
	if(rc != -1)
	{
        if(!rc)
			return NX_EPERM;
		else
			return NX_ERROR;
	}

	rc = fatfs_node_find_dirent(snode, sname, &dent, &off, &len);
	if(rc)
		return NX_ERROR;

	rc = fatfs_node_del_dirent(snode, sname, off, len);
	if(rc)
		return NX_ERROR;

	rc = fatfs_node_add_dirent(dnode, dname, &dent);
	if(rc)
		return NX_ERROR;

	return NX_EOK;
}

NX_PRIVATE NX_Error FatfsCreateDir(NX_FileNode * dn, const char *name, NX_U32 mode)
{
	int rc;
	NX_U8 fileattr;
	NX_U32 off, len;
	NX_U32 year, mon, day, hour, min, sec;
	struct fat_dirent_t dent;
	struct fatfs_node_t *dnode = dn->data;

	rc = fatfs_node_find_dirent(dnode, name, &dent, &off, &len);
	if(rc != -1)
	{
		if(!rc)
			return NX_EPERM;
		else
			return NX_ERROR;
	}

	fatfs_current_timestamp(&year, &mon, &day, &hour, &min, &sec);

	fileattr = FAT_DIRENT_SUBDIR;
	if(!(mode & (NX_FILE_MODE_WRITE)))
	{
		fileattr |= FAT_DIRENT_READONLY;
	}

	NX_MemSet(&dent, 0, sizeof(dent));
	dent.file_attributes = fileattr;
	dent.create_time_millisecs = 0;
	dent.create_time_seconds = sec;
	dent.create_time_minutes = min;
	dent.create_time_hours = hour;
	dent.create_date_day = day;
	dent.create_date_month = mon;
	dent.create_date_year = year;
	dent.laccess_date_day = day;
	dent.laccess_date_month = mon;
	dent.laccess_date_year = year;
	dent.first_cluster_hi = 0;
	dent.lmodify_time_seconds = sec;
	dent.lmodify_time_minutes = min;
	dent.lmodify_time_hours = hour;
	dent.lmodify_date_day = day;
	dent.lmodify_date_month = mon;
	dent.lmodify_date_year = year;
	dent.first_cluster_lo = 0;
	dent.file_size = 0;

	rc = fatfs_node_add_dirent(dnode, name, &dent);
	if(rc)
		return NX_ERROR;

	return NX_EOK;
}

NX_PRIVATE NX_Error FatfsDeleteDir(NX_FileNode * dn, NX_FileNode * n, const char * name)
{
	int rc;
	NX_U32 off, len, clust;
	struct fat_dirent_t dent;
	struct fatfs_node_t *dnode = dn->data;
	struct fatfs_node_t *node = n->data;

	rc = fatfs_node_find_dirent(dnode, name, &dent, &off, &len);
	if(rc)
		return NX_ERROR;

	if(dnode->ctrl->type == FAT_TYPE_32)
	{
		clust = NX_Le16ToCpu(dent.first_cluster_hi);
		clust = clust << 16;
	}
	else
	{
		clust = 0;
	}
	clust |= NX_Le16ToCpu(dent.first_cluster_lo);

	if(node->first_cluster != clust)
		return NX_EFAULT;

	rc = fatfs_control_truncate_clusters(node->ctrl, clust);
	if(rc)
		return NX_ERROR;

	rc = fatfs_node_del_dirent(dnode, name, off, len);
	if(rc)
		return NX_ERROR;

	return NX_EOK;
}

NX_PRIVATE NX_Error FatfsSetMode(NX_FileNode * n, NX_U32 mode)
{
	struct fatfs_node_t *node = n->data;
    NX_U32 year, month, day, hour, min, second;

	n->mode &= ~(NX_FILE_MODE_MASK);
	n->mode |= mode;

    fatfs_current_timestamp(&year, &month, &day, &hour, &min, &second);
    n->atime = fatfs_pack_timestamp(year, month, day, hour, min, second);

	node->parent_dent_dirty = NX_True;

	return NX_EOK;
}

NX_PRIVATE NX_FileSystem fat = {
	.name		= "fat",
	.mount		= FatfsMount,
	.unmount	= FatfsUnmount,
	.mountSync	= FatfsMountSync,
	.getNode	= FatfsGetNode,
	.putNode	= FatfsPutNode,
	.read		= FatfsRead,
	.write		= FatfsWrite,
	.truncate	= FatfsTruncate,
	.sync		= FatfsSync,
	.readDir	= FatfsReadDir,
	.lookup		= FatfsLookup,
	.create		= FatfsCreate,
	.delete		= FatfsDelete,
	.rename		= FatfsRename,
	.createDir	= FatfsCreateDir,
	.deleteDir	= FatfsDeleteDir,
	.setMode	= FatfsSetMode,
};

NX_PRIVATE void NX_FileSystemFatInit(void)
{
	NX_FileSystemRegister(&fat);
}

NX_PRIVATE void NX_FileSystemFatExit(void)
{
	NX_FileSystemUnregister(&fat);
}

NX_FS_INIT(NX_FileSystemFatInit);
NX_FS_EXIT(NX_FileSystemFatExit);
